package itsec1.customermanagement;


import itsec1.api.cardmanagement.CardManagementListener;
import itsec1.api.services.ServiceListener;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

public class CustomerManagement {

	private static final String SSL_PROTOCOL = "TLS";
	private static final int CARD_TCP_PORT = 25000;
	public static final int CARDREQUEST_TCP_PORT = 30000;
	private static final int SERVICE_TCP_PORT = 35000;
	private static final char[] PASSPHRASE = "customerManager1234"
			.toCharArray();
	private static final String KS_LOCATION = "keys/customerkeys.jks";
	private static final String KS_ALGORITHM = "SunX509";
	private static final String KS_TYPE = "JKS";
	private static final String CARD_TS_LOCATION = "keys/cardtruststore.jks";
	private static final String SERVICE_TS_LOCATION = "keys/servicetruststore.jks";
	private static final String KEY_ALIAS = "customermanagement";
	private static final String SECURITY_PROVIDER = "BC";
	private static final String SECURITY_PROVIDER_CLASS = "org.bouncycastle.jce.provider.BouncyCastleProvider";

	private ExecutorService executorService = Executors.newCachedThreadPool();
	private Hashtable<String,Integer> invalidLoginTries= new Hashtable<String,Integer>();
	private SSLServerSocket cardManagementSocket, serviceManagementSocket;
	private Connection conn = null;
	private RSAEncryption encryptor;
	private KeyPair keyPair;

	/**
	 * Create new customer management instance with all security features
	 */
	public CustomerManagement() throws Exception {
		@SuppressWarnings("rawtypes")
		Class c = Class.forName(SECURITY_PROVIDER_CLASS);
		Security.addProvider((java.security.Provider) c.newInstance());

		KeyStore ks = KeyStore.getInstance(KS_TYPE);
		ks.load(new FileInputStream(KS_LOCATION), PASSPHRASE);
		Key key = ks.getKey(KEY_ALIAS, PASSPHRASE);

		if (key instanceof PrivateKey) {
			Certificate cert = ks.getCertificate(KEY_ALIAS);
			PublicKey publicKey = cert.getPublicKey();

			this.keyPair = new KeyPair(publicKey, (PrivateKey) key);
		} else {
			throw new GeneralSecurityException("Could not load private key");
		}

		this.encryptor = new RSAEncryption(this.getKeyPair());
	}

	public void start() throws IOException {
		// create socket for communication with card management
		ServerSocketFactory ssfCard = this
				.createServerSocketFactory(CARD_TS_LOCATION);
		this.cardManagementSocket = (SSLServerSocket) ssfCard
				.createServerSocket(CARD_TCP_PORT);
		this.cardManagementSocket.setNeedClientAuth(true);
		// start listening for request from card management
		this.executorService.execute(new CardManagementListener(this,
				this.cardManagementSocket));

		// create socket for communication with services
		ServerSocketFactory ssfService = this
				.createServerSocketFactory(SERVICE_TS_LOCATION);
		this.serviceManagementSocket = (SSLServerSocket) ssfService
				.createServerSocket(SERVICE_TCP_PORT);
		this.serviceManagementSocket.setNeedClientAuth(true);
		// start listening for request from services
		this.executorService.execute(new ServiceListener(this,
				this.serviceManagementSocket));

		this.conn = getDBConnection();
	}

	public void stop() throws IOException {
		this.cardManagementSocket.close();
		System.out.println("CardManagement shutdown");
		this.serviceManagementSocket.close();
		System.out.println("Service shutdown");
		this.executorService.shutdownNow();

		try {
			// if db connection still up -> disconnect
			if (conn != null && !conn.isClosed())
				conn.close();
		} catch (SQLException e) {
		}
	}

	public byte[] encrypt(String data) throws IOException {
		return encryptor.encrypt(data);
	}

	public byte[] encrypt(byte[] data) throws IOException {
		return encryptor.encrypt(data);
	}

	public String decrypt(byte[] edata) throws IOException {
		return encryptor.decrypt(edata);
	}

	public byte[] decryptAsBytes(byte[] edata) throws IOException {
		return encryptor.decryptAsBytes(edata);
	}

	public Connection getDBConnection() {
		Connection conn = null;

		try {
			/*
			 * System.setProperty("javax.net.ssl.keyStore","keys/dbkeys.jks");
			 * System
			 * .setProperty("javax.net.ssl.keyStorePassword","DBcustomerManager"
			 * );
			 * System.setProperty("javax.net.ssl.trustStore","keys/dbkeys.jks");
			 * System
			 * .setProperty("javax.net.ssl.trustStorePassword","DBcustomerManager"
			 * );
			 * 
			 * conn =
			 * DriverManager.getConnection("jdbc:mysql://localhost/customer?" +
			 * "user=customermanager&password=DBcustomerManager&useSSL=true&requireSSL=true&verifyServerCerticate=false"
			 * );
			 */
			conn = DriverManager
					.getConnection("jdbc:mysql://localhost/customer?"
							+ "user=customermanager&password=DBcustomerManager");
		} catch (SQLException ex) {
			// handle any errors
			System.out.println("SQLException: " + ex.getMessage());
			System.out.println("SQLState: " + ex.getSQLState());
			System.out.println("VendorError: " + ex.getErrorCode());
		}
		return conn;
	}

	public ExecutorService getExecutorService() {
		return this.executorService;
	}

	private ServerSocketFactory createServerSocketFactory(String tsLocation) {
		SSLServerSocketFactory ssf = null;

		try {
			// setting up key manager to do server authentication
			KeyManagerFactory kmf = KeyManagerFactory.getInstance(KS_ALGORITHM);
			KeyStore ks = KeyStore.getInstance(KS_TYPE);
			ks.load(new FileInputStream(KS_LOCATION), PASSPHRASE);
			kmf.init(ks, PASSPHRASE);

			TrustManagerFactory tmf = TrustManagerFactory
					.getInstance(KS_ALGORITHM);
			KeyStore ts = KeyStore.getInstance(KS_TYPE);
			ts.load(new FileInputStream(tsLocation), PASSPHRASE);
			tmf.init(ts);

			SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL);
			ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
			ssf = ctx.getServerSocketFactory();
		} catch (Exception e) {
			System.out.println("Error: " + e.getMessage());
			e.printStackTrace();
		}

		return ssf;
	}
	
	public static SocketFactory createSocketFactory(String tsLocation) {
		SSLSocketFactory sf = null;

		try {
			// setting up key manager to do server authentication
			KeyManagerFactory kmf = KeyManagerFactory.getInstance(KS_ALGORITHM);
			KeyStore ks = KeyStore.getInstance(KS_TYPE);
			ks.load(new FileInputStream(KS_LOCATION), PASSPHRASE);
			kmf.init(ks, PASSPHRASE);

			TrustManagerFactory tmf = TrustManagerFactory
					.getInstance(KS_ALGORITHM);
			KeyStore ts = KeyStore.getInstance(KS_TYPE);
			ts.load(new FileInputStream(tsLocation), PASSPHRASE);
			tmf.init(ts);

			SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL);
			ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
			sf = ctx.getSocketFactory();
		} catch (Exception e) {
			System.out.println("Error: " + e.getMessage());
			e.printStackTrace();
		}

		return sf;
	}

	public KeyPair getKeyPair() {
		return keyPair;
	}

	public Signature getSignature() {
		try {
			return Signature
			.getInstance("SHA1withRSA", SECURITY_PROVIDER);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public Hashtable<String,Integer> getInvalidLoginTries(){
		return invalidLoginTries;
	}

	public static void main(final String[] args) {
		CustomerManagement cm = null;

		try {
			cm = new CustomerManagement();
			cm.start();
			System.out
					.println("Customer Management is running. Press enter to exit");
			BufferedReader in = new BufferedReader(new InputStreamReader(
					System.in));
			try {
				in.readLine();
			} catch (final Exception e) {
				// nothing
			}
		} catch (Exception e) {
			System.out.print("Unexpected exception: " + e.getMessage());
			if (e.getCause() != null)
				System.out.print(MessageFormat.format(" ({0})", e.getCause()
						.getMessage()));
		} finally {
			if (cm != null) {
				try {
					cm.stop();
				} catch (Exception ex) {
					System.out.println("Could't stop Customer Management: "
							+ ex.getMessage());

				}
			}
		}

	}

}
